BOKOSCU

Оптимизация кода: малые правки — большой эффект

Тут буду писать небольшие заметки по производительности в Node.js (и фрейморках), если кто-то желает присоединиться - добро пожаловать.

1. Используй_Map_вместо обычных объектов

При работе с ассоциативными массивами многие используют обычные объекты {}. Однако Map работает быстрее при частых операциях поиска и вставки:

const map = new Map();
map.set("key", "value");
console.log(map.get("key"));

В отличие от обычных объектов, Map сохраняет порядок вставки и работает быстрее при большом количестве записей.

2. Минимизируй количество асинхронных операций

Часто в коде можно встретить последовательные вызовы await, которые замедляют выполнение:

const user = await getUser();
const posts = await getPosts(user.id);

Оптимизируем параллельным выполнением:

const [user, posts] = await Promise.all([getUser(), getPosts(user.id)]);

3. Используй потоки (Streams) для обработки данных

При работе с большими файлами не загружайте их полностью в память:

const fs = require("fs");
fs.createReadStream("bigfile.txt").pipe(process.stdout);

Это позволит читать и обрабатывать данные по частям, снижая потребление памяти.

4. Оптимизируй циклы

При итерации по массивам используйте for вместо forEach для максимальной производительности:

for (let i = 0; i < array.length; i++) {
  console.log(array[i]);
}

Это работает быстрее, так как избегает создания дополнительных замыканий. Иной раз forEach может быть быстрее, т.к. является методом к массиву.

5. Кэшируй результаты вычислений

Если функция выполняет дорогостоящие операции, кэшируй результаты:

const cache = new Map();
function expensiveOperation(input: string) {
  if (cache.has(input)) return cache.get(input);
  const result = compute(input);
  cache.set(input, result);
  return result;
}

Иной раз может быть не корректым, но использовать Redis или внешний источник правды это маст хэв практис.

6. Объединение require/import в начале файла

Node.js использует синхронную загрузку модулей. Разнесённые require() по коду мешают прогнозируемости и задерживают исполнение. Лучше загружать все зависимости в начале, чтобы избежать неожиданных блокировок и ускорить startup.

7. Избегай .bind(), используй замыкания

Каждый вызов .bind() создаёт новую функцию:

// Плохо
someArray.forEach(this.handle.bind(this));

// Лучше
const handler = (x) => this.handle(x);
someArray.forEach(handler);

Меньше аллокаций, особенно в циклах, особенно во вложенных циклах.

8. Object.create(null) вместо обычного объекта

Это создаёт объект без прототипа:

const map = Object.create(null);
map["key"] = "value";

Нет hasOwnProperty, toString, и других “лишних” свойств. Полезно, когда используешь объект как хэш-карту.

9. Осторожно с process.nextTick() ⚠

nextTick() выполняется до любых I/O. Если ты вызываешь process.nextTick() внутри самого nextTick, ты создаёшь бесконечную микро-очередь, блокирующую все I/O события:

function loop() {
  process.nextTick(loop); // Может заблокировать event loop
}

-Jame